package cn.hzc.fortess.spi;

import cn.hutool.extra.tokenizer.TokenizerException;
import cn.hzc.fortess.core.common.constant.Const;
import cn.hzc.fortess.core.common.constant.JwtConstants;
import cn.hzc.fortess.core.common.exception.ServiceException;
import cn.hzc.fortess.core.shiro.ShiroKit;
import cn.hzc.fortess.core.util.JwtTokenUtil;
import cn.hzc.fortess.spi.bean.JwtExceptionEnum;
import cn.hzc.fortess.spi.bean.JwtResourceData;
import cn.hzc.fortess.spi.bean.JwtResponse;
import cn.hzc.fortess.spi.bean.JwtUserInfo;
import cn.hzc.fortess.system.common.EnableStatus;
import cn.hzc.fortess.system.common.ResourceScope;
import cn.hzc.fortess.system.entity.*;
import cn.hzc.fortess.system.service.*;
import cn.hzc.fortess.system.vo.ResourceAttrVO;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import java.util.*;
import java.util.stream.Collectors;

@RequestMapping("/authspi")
@RestController
public class AuthService {

    private static final Logger log = LoggerFactory.getLogger("AUTH-SPI-SERVICE");

    @Autowired
    UserService userService;
    @Autowired
    MenuService menuService;
    @Autowired
    JwtTokenService jwtTokenService;
    @Autowired
    DeptService deptService;
    @Autowired
    RoleService roleService;
    @Autowired
    ResourceAttrService resourceAttrService;
    @Autowired
    LesseeService lesseeService;

    /**
     * 认证并返回token
     */
    @RequestMapping(value = "/auth2token", method = RequestMethod.POST)
    JwtResponse<String> loginAuth(@RequestParam("account") String account,
                                  @RequestParam("password") String password) {
        log.info("jwt::auth2token,account:{},pwd:*** ", account);

        //超级管理员禁用该功能
        if (Const.ADMIN_LOGIN_NAME.equals(account)) {
            return new JwtResponse(JwtExceptionEnum.USER_LOGIN_FAIL);
        }
        final User user = userService.getByLoginName(account);
        if (user == null || Objects.equals(EnableStatus.DISABLE.getStatus(), user.getStatus())) {
            log.error("jwt auth2token,user not found. {}", account);
            return new JwtResponse(JwtExceptionEnum.USER_LOGIN_FAIL);
        }
        if (!user.getPassword().equals(ShiroKit.md5(password, user.getSalt()))) {
            log.error("jwt auth2token,user password error. {}", account);
            return new JwtResponse(JwtExceptionEnum.USER_LOGIN_FAIL);
        }


        final JwtResponse response = new JwtResponse();
        response.setData(jwtTokenService.generateJwtToken(user.getUserId()));
        log.info("jwt auth2token success, account:{},token:{}", account, response.getData());
        return response;
    }

    private void checkTokenStatus(String jwtToken) throws TokenizerException {
        if (jwtTokenService.isTokenExpired(jwtToken)) {
            log.warn("jwt token is expired.{}", jwtToken == null ? "[null]" : jwtToken);
            throw new TokenizerException("Token过期");
        }
    }

    private Integer getUserIdByToken(String jwtToken) {
        return Integer.parseInt(JwtTokenUtil.getUserIdFromToken(jwtToken));
    }

    /**
     * 退出认证
     */
    @RequestMapping(value = "/logout", method = RequestMethod.POST)
    JwtResponse logout() {
        String token = JwtConstants.getJwtHeaderToken();
        log.info("jwt::logout,token:{} ", token);

        if (token != null) {
            jwtTokenService.removeJwtToken(getUserIdByToken(token));
        }
        return new JwtResponse();
    }

    /**
     * 验证token是否过期
     */
    @RequestMapping(value = "/checktoken", method = RequestMethod.POST)
    JwtResponse checkToken() {
        String token = JwtConstants.getJwtHeaderToken();
        log.info("jwt::checkToken,token:{} ", token);

        try {
            checkTokenStatus(token);
        } catch (TokenizerException e) {
            return new JwtResponse(JwtExceptionEnum.TOKEN_EXPIRED);
        }
        return new JwtResponse();
    }


    /**
     * 获取登录用户信息
     */
    @RequestMapping(value = "/userinfo/get", method = RequestMethod.POST)
    JwtResponse<JwtUserInfo> getLoginUserInfo() {
        String token = JwtConstants.getJwtHeaderToken();
        log.info("jwt::getuserinfo,token:{} ", token);

        try {
            checkTokenStatus(token);
        } catch (TokenizerException e) {
            return new JwtResponse(JwtExceptionEnum.TOKEN_EXPIRED);
        }
        JwtResponse jwtResponse = new JwtResponse();
        User user = userService.getById(getUserIdByToken(token));
        JwtUserInfo jwtUserInfo = new JwtUserInfo();
        BeanUtils.copyProperties(user, jwtUserInfo);
        final Dept userDept = deptService.getById(user.getDeptId());
        jwtUserInfo.setDeptCode(userDept.getParentIds() + "[" + userDept.getId() + "]");

        List<Role> roles = roleService.getByUser(user.getUserId());
        if (!CollectionUtils.isEmpty(roles)) {
            jwtUserInfo.setRoles(roles.parallelStream().map(Role::getRoleName).distinct().collect(Collectors.toList()));
        }

        jwtResponse.setData(jwtUserInfo);


        return jwtResponse;
    }

    /**
     * 修改密码
     */
    @RequestMapping(value = "/modifpwd", method = RequestMethod.POST)
    JwtResponse modifPwd(@RequestParam("oldpwd") String oldpwd, @RequestParam("newpwd") String newpwd) {
        if (StringUtils.isBlank(newpwd) || StringUtils.isBlank(oldpwd)) {
            return new JwtResponse((JwtExceptionEnum.REQUEST_NULL));
        }
        String token = JwtConstants.getJwtHeaderToken();
        log.info("jwt::modifpwd,token:{} ", token);

        try {
            checkTokenStatus(token);
            userService.changePwd(null, getUserIdByToken(token), oldpwd, newpwd);
        } catch (TokenizerException e) {
            return new JwtResponse(JwtExceptionEnum.TOKEN_EXPIRED);
        } catch (ServiceException e) {
            return new JwtResponse(JwtExceptionEnum.PWD_ORIGINAL_ERROR);
        }
        return new JwtResponse();
    }

    /**
     * 资源鉴权，资源是否有拥有可操作的权限
     */
    @RequestMapping(value = "/resource/auth", method = RequestMethod.POST)
    JwtResponse resourceAuth(@RequestParam("authIdent") String authIdent) {
        if (StringUtils.isBlank(authIdent)) {
            return new JwtResponse((JwtExceptionEnum.REQUEST_NULL));
        }
        String token = JwtConstants.getJwtHeaderToken();
        log.info("jwt::resource auth,authIdent:{},token:{} ", authIdent, token);

        try {
            checkTokenStatus(token);
        } catch (TokenizerException e) {
            return new JwtResponse(JwtExceptionEnum.TOKEN_EXPIRED);
        }
        if (!userService.hasResourceAuth(getUserIdByToken(token), authIdent)) {
            return new JwtResponse(JwtExceptionEnum.AUTH_FAIL);

        }
        return new JwtResponse();
    }

    /**
     * 获取菜单的数据权限
     */
    @RequestMapping(value = "/resource/fields", method = RequestMethod.POST)
    JwtResponse resourceData(@RequestParam("authIdent") String authIdent) {

        if (StringUtils.isBlank(authIdent)) {
            return new JwtResponse((JwtExceptionEnum.REQUEST_NULL));
        }

        String token = JwtConstants.getJwtHeaderToken();
        log.info("jwt::resource data,authIdent:{},token:{} ", authIdent, token);

        try {
            checkTokenStatus(token);
        } catch (TokenizerException e) {
            return new JwtResponse(JwtExceptionEnum.TOKEN_EXPIRED);
        }
        Integer userId = getUserIdByToken(token);

        if (!userService.hasResourceAuth(userId, authIdent)) {
            return new JwtResponse(JwtExceptionEnum.AUTH_FAIL);
        }
        JwtResponse response = new JwtResponse();
        JwtResourceData data = new JwtResourceData();
        Menu menu = menuService.getByAuthIdent(authIdent);
        Integer scope = menuService.getResourceScopeById(menu.getId());
        data.setScope(scope == null ? ResourceScope.PUBLIC_READ_WRITE.getCode() : scope);
        response.setData(data);

        //查字段，查询用户角色列表
        List<Role> roles = roleService.getByUser(userId);
        if (CollectionUtils.isEmpty(roles)) {
            return response;

        }
        List<ResourceAttrVO> attrs = resourceAttrService.getRoleResourceAttrList(menu.getId(), roles.parallelStream().map(Role::getId).collect(Collectors.toList()));
        if (CollectionUtils.isEmpty(attrs)) {
            return response;

        }
        //同资源可能有不同的字段数据权限，用户有AB两个角色，A角色只读，B角色读写，最终结果为"读写"，读写>只读>隐藏
        data.setFields(buildResourceField(attrs));

        return response;
    }

    private Map<String, String> buildResourceField(List<ResourceAttrVO> attrs) {
        //先根据优先级降序排序下
        attrs.sort(new AttrprioritPComparator());
        final Map<String, String> map = Maps.newHashMap();
        for (ResourceAttrVO vo : attrs) {
            if (map.containsKey(vo.getAttrCode())) {
                continue;
            }
            map.put(vo.getAttrCode(), vo.getAttrAuth());
        }
        return map;
    }


    private static class AttrprioritPComparator implements Comparator<ResourceAttrVO> {

        @Override
        public int compare(ResourceAttrVO o1, ResourceAttrVO o2) {
            return o2.getPriority() - o1.getPriority();
        }
    }

    /**
     * 获取项目的菜单资源列表
     */
    @RequestMapping(value = "/menu", method = RequestMethod.POST)
    JwtResponse getResources(@RequestParam(value = "projectCode", required = false) String projectCode) {

        String token = JwtConstants.getJwtHeaderToken();
        log.info("jwt::resources,token:{} ", token);

        try {
            checkTokenStatus(token);
        } catch (TokenizerException e) {
            return new JwtResponse(JwtExceptionEnum.TOKEN_EXPIRED);
        }
        JwtResponse jwtResponse = new JwtResponse();
        jwtResponse.setData(menuService.getUserMenus(projectCode, getUserIdByToken(token)));
        return jwtResponse;
    }
}
